# Copyright 2018 Maco
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
'''
A blockchain contains the following parameters:
{
    'index': 1, #start with 1
    'timestamp': 1523276533.481867,
    'block_size': '100KB',
    'transaction_count':1000,
    'transactions': [ #ransaction list
        {
            'sender': 'sdf4nqy59v832y52jkd50958',
            'recipient': 'sdf4xm902u3r932rc2c9m4r0',
            'amount': 10
        }
    ],
    'proof': 324984774000, #Proof of work
    'hash': '?',
    'prev_hash':'dfr432defa5fb0a30e26e83b2acew3d29e1b161e5c1fa7424rf3043362938bdse2'
}
'''
import time
import json
from datetime import datetime
import hashlib

from blockchain.core import proof as sproof
from blockchain.core.hash import hash_dict, hash_str, hash_block

__all__ = ['Bchain']


class Blockchain(object):

    def __init__(self):
        self.chain = list()
        # 记录当前的交易
        self.transactions = list()
        # 记录每个交易的hash值
        self.tx_hash_list = list()

    def __new__(cls, *args):
        '''Create single object.'''
        if not hasattr(cls, '_instance'):
            cls._instance = object.__new__(cls)
        return cls._instance

    @classmethod
    def instance(cls):
        '''Create single object.'''
        if not hasattr(cls, '_instance'):
            cls._instance = object.__new__(cls)
        return cls._instance

    def new_block(self, proof, prev_hash=None):
        '''
        创建新的块
        :param proof: <int>工作量证明
        :param prev_hash: if not prev_hash else self.hash(last_block)
        '''
        block = {
            'index': len(self.chain) + 1,
            'timestamp': time.time(),
            'datetime': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
            'transactions': self.transactions,
            'tx_count': len(self.transactions),
            'proof': proof,
            'prev_hash': prev_hash or self.hash(self.chain[-1]),
        }
        # 重置交易列表和交易列表的hash值
        self.transactions = list()
        self.tx_hash_list = list()
        block['hash'] = self.hash(block)
        self.chain.append(block)
        return block

    def add_block(self, block):
        '''
        添加新的块
        :param block: <dict>
        '''
        if block['index'] == (self.last_block['index'] + 1):
            # 重置交易列表和交易列表的hash值
            self.transactions = list()
            self.tx_hash_list = list()
            self.chain.append(block)

    def new_transaction(self, sender, recipient, amount, timestamp=None):
        '''
        生成交易信息并将其添加到要挖掘的下一个块中
        :param sender: <str> 发送者地址
        :param recipient: <str> 接受者地址
        :param amount: <int> 金额
        :param timestamp: <float> 交易时间，可选，同步需要使用
        :return: <int> 最新一块的index.
        '''
        data = {
            'sender': sender,
            'recipient': recipient,
            'amount': amount,
            'timestamp': timestamp or time.time()
        }
        self.transactions.append(data)
        self.tx_hash_list.append(hash_dict(data))
        return self.last_block['index'] + 1

    def get_tx_count(self):
        '''
        获取当前的交易总数
        :reutrn: <int>
        '''
        return len(self.transactions)

    @staticmethod
    def vaild_chain(chain):
        '''
        验证给出的区块链
        :param chain: <list> 区块链，数组类型
        :return: <bool> True 验证成功, False 验证失败
        '''
        # 第一块是创世纪块，没有上一块的hash值
        last_block = chain[0]
        index = 1
        while index < len(chain):
            block = chain[index]
            # 验证上一块的hash值是否正确
            if block['prev_hash'] != hash_block(last_block):
                return False
            # 工作量证明验证
            if not sproof.check_block_proof(block):
                return False
            index += 1
            last_block = block
        return True

    @property
    def last_block(self):
        '''
        获取最新区块
        :return: block dict
        '''
        return self.chain[-1]

    @staticmethod
    def hash(block):
        return hash_block(block)

Bchain = Blockchain().instance()
# 创建创世纪块(第一块)
Bchain.new_block(100, '1')
